home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 4: GNU Archives / Linux Cubed Series 4 - GNU Archives.iso / gnu / textutil.19 / textutil / textutils-1.19 / src / split.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-04-24  |  12.7 KB  |  543 lines

  1. /* split.c -- split a file into pieces.
  2.    Copyright (C) 88, 91, 95, 1996 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
  17.  
  18. /* By tege@sics.se, with rms.
  19.  
  20.    To do:
  21.    * Implement -t CHAR or -t REGEX to specify break characters other
  22.      than newline. */
  23.  
  24. #include <config.h>
  25.  
  26. #include <stdio.h>
  27. #include <getopt.h>
  28. #include <sys/types.h>
  29.  
  30. #if HAVE_LIMITS_H
  31. # include <limits.h>
  32. #endif
  33.  
  34. #ifndef UINT_MAX
  35. # define UINT_MAX ((unsigned int) ~(unsigned int) 0)
  36. #endif
  37.  
  38. #ifndef INT_MAX
  39. # define INT_MAX ((int) (UINT_MAX >> 1))
  40. #endif
  41.  
  42. #include "system.h"
  43. #include "error.h"
  44. #include "xstrtol.h"
  45.  
  46. char *xmalloc ();
  47. int full_write ();
  48. int safe_read ();
  49.  
  50. /* The name this program was run with. */
  51. char *program_name;
  52.  
  53. /* Base name of output files.  */
  54. static char *outfile;
  55.  
  56. /* Pointer to the end of the prefix in OUTFILE.
  57.    Suffixes are inserted here.  */
  58. static char *outfile_mid;
  59.  
  60. /* Pointer to the end of OUTFILE. */
  61. static char *outfile_end;
  62.  
  63. /* Name of input file.  May be "-".  */
  64. static char *infile;
  65.  
  66. /* Descriptor on which input file is open.  */
  67. static int input_desc;
  68.  
  69. /* Descriptor on which output file is open.  */
  70. static int output_desc;
  71.  
  72. /* If nonzero, display usage information and exit.  */
  73. static int show_help;
  74.  
  75. /* If nonzero, print the version on standard output then exit.  */
  76. static int show_version;
  77.  
  78. /* If nonzero, print a diagnostic on standard error just before each
  79.    output file is opened. */
  80. static int verbose;
  81.  
  82. static struct option const longopts[] =
  83. {
  84.   {"bytes", required_argument, NULL, 'b'},
  85.   {"lines", required_argument, NULL, 'l'},
  86.   {"line-bytes", required_argument, NULL, 'C'},
  87.   {"verbose", no_argument, NULL, 2},
  88.   {"help", no_argument, &show_help, 1},
  89.   {"version", no_argument, &show_version, 1},
  90.   {NULL, 0, NULL, 0}
  91. };
  92.  
  93. static void
  94. usage (int status, const char *reason)
  95. {
  96.   if (reason != NULL)
  97.     fprintf (stderr, "%s: %s\n", program_name, reason);
  98.  
  99.   if (status != 0)
  100.     fprintf (stderr, _("Try `%s --help' for more information.\n"),
  101.          program_name);
  102.   else
  103.     {
  104.       printf (_("\
  105. Usage: %s [OPTION] [INPUT [PREFIX]]\n\
  106. "),
  107.           program_name);
  108.     printf (_("\
  109. Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default\n\
  110. PREFIX is `x'.  With no INPUT, or when INPUT is -, read standard input.\n\
  111. \n\
  112.   -C, --line-bytes=SIZE   put at most SIZE bytes of lines per output file\n\
  113.   -b, --bytes=SIZE        put SIZE bytes per output file\n\
  114.   -l, --lines=NUMBER      put NUMBER lines per output file\n\
  115.       --verbose           print a diagnostic to standard error just\n\
  116.                 before each output file is opened\n\
  117.   -NUMBER                 same as -l NUMBER\n\
  118.       --help              display this help and exit\n\
  119.       --version           output version information and exit\n\
  120. \n\
  121. SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\
  122. "));
  123.     }
  124.   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
  125. }
  126.  
  127. /* Compute the next sequential output file name suffix and store it
  128.    into the string `outfile' at the position pointed to by `outfile_mid'.  */
  129.  
  130. static void
  131. next_file_name (void)
  132. {
  133.   int x;
  134.   char *ne;
  135.   unsigned int i;
  136.  
  137.   static int first_call = 1;
  138.  
  139.   /* Status for outfile name generation.  */
  140.   static unsigned outfile_count = 0;
  141.   static unsigned outfile_name_limit = 25 * 26;
  142.   static unsigned outfile_name_generation = 1;
  143.  
  144.   if (!first_call)
  145.     outfile_count++;
  146.   first_call = 0;
  147.   if (outfile_count < outfile_name_limit)
  148.     {
  149.       for (ne = outfile_end - 1; ; ne--)
  150.     {
  151.       x = *ne;
  152.       if (x != 'z')
  153.         break;
  154.       *ne = 'a';
  155.     }
  156.       *ne = x + 1;
  157.       return;
  158.     }
  159.  
  160.   outfile_count = 0;
  161.   outfile_name_limit *= 26;
  162.   outfile_name_generation++;
  163.   *outfile_mid++ = 'z';
  164.   for (i = 0; i <= outfile_name_generation; i++)
  165.     outfile_mid[i] = 'a';
  166.   outfile_end += 2;
  167. }
  168.  
  169. /* Write BYTES bytes at BP to an output file.
  170.    If NEW_FILE_FLAG is nonzero, open the next output file.
  171.    Otherwise add to the same output file already in use.  */
  172.  
  173. static void
  174. cwrite (int new_file_flag, const char *bp, int bytes)
  175. {
  176.   if (new_file_flag)
  177.     {
  178.       if (output_desc >= 0 && close (output_desc) < 0)
  179.     error (EXIT_FAILURE, errno, "%s", outfile);
  180.  
  181.       next_file_name ();
  182.       if (verbose)
  183.     fprintf (stderr, _("creating file `%s'\n"), outfile);
  184.       output_desc = open (outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  185.       if (output_desc < 0)
  186.     error (EXIT_FAILURE, errno, "%s", outfile);
  187.     }
  188.   if (full_write (output_desc, bp, bytes) < 0)
  189.     error (EXIT_FAILURE, errno, "%s", outfile);
  190. }
  191.  
  192. /* Read NCHARS bytes from the input file into BUF.
  193.    Return the number of bytes successfully read.
  194.    If this is less than NCHARS, do not call `stdread' again.  */
  195.  
  196. static int
  197. stdread (char *buf, int nchars)
  198. {
  199.   int n_read;
  200.   int to_be_read = nchars;
  201.  
  202.   while (to_be_read)
  203.     {
  204.       n_read = safe_read (input_desc, buf, to_be_read);
  205.       if (n_read < 0)
  206.     return -1;
  207.       if (n_read == 0)
  208.     break;
  209.       to_be_read -= n_read;
  210.       buf += n_read;
  211.     }
  212.   return nchars - to_be_read;
  213. }
  214.  
  215. /* Split into pieces of exactly NCHARS bytes.
  216.    Use buffer BUF, whose size is BUFSIZE.  */
  217.  
  218. static void
  219. bytes_split (int nchars, char *buf, int bufsize)
  220. {
  221.   int n_read;
  222.   int new_file_flag = 1;
  223.   int to_read;
  224.   int to_write = nchars;
  225.   char *bp_out;
  226.  
  227.   do
  228.     {
  229.       n_read = stdread (buf, bufsize);
  230.       if (n_read < 0)
  231.         error (EXIT_FAILURE, errno, "%s", infile);
  232.       bp_out = buf;
  233.       to_read = n_read;
  234.       for (;;)
  235.     {
  236.       if (to_read < to_write)
  237.         {
  238.           if (to_read)    /* do not write 0 bytes! */
  239.         {
  240.           cwrite (new_file_flag, bp_out, to_read);
  241.           to_write -= to_read;
  242.           new_file_flag = 0;
  243.         }
  244.           break;
  245.         }
  246.       else
  247.         {
  248.           cwrite (new_file_flag, bp_out, to_write);
  249.           bp_out += to_write;
  250.           to_read -= to_write;
  251.           new_file_flag = 1;
  252.           to_write = nchars;
  253.         }
  254.     }
  255.     }
  256.   while (n_read == bufsize);
  257. }
  258.  
  259. /* Split into pieces of exactly NLINES lines.
  260.    Use buffer BUF, whose size is BUFSIZE.  */
  261.  
  262. static void
  263. lines_split (int nlines, char *buf, int bufsize)
  264. {
  265.   int n_read;
  266.   char *bp, *bp_out, *eob;
  267.   int new_file_flag = 1;
  268.   int n = 0;
  269.  
  270.   do
  271.     {
  272.       n_read = stdread (buf, bufsize);
  273.       if (n_read < 0)
  274.     error (EXIT_FAILURE, errno, "%s", infile);
  275.       bp = bp_out = buf;
  276.       eob = bp + n_read;
  277.       *eob = '\n';
  278.       for (;;)
  279.     {
  280.       while (*bp++ != '\n')
  281.         ;            /* this semicolon takes most of the time */
  282.       if (bp > eob)
  283.         {
  284.           if (eob != bp_out) /* do not write 0 bytes! */
  285.         {
  286.           cwrite (new_file_flag, bp_out, eob - bp_out);
  287.           new_file_flag = 0;
  288.         }
  289.           break;
  290.         }
  291.       else
  292.         if (++n >= nlines)
  293.           {
  294.         cwrite (new_file_flag, bp_out, bp - bp_out);
  295.         bp_out = bp;
  296.         new_file_flag = 1;
  297.         n = 0;
  298.           }
  299.     }
  300.     }
  301.   while (n_read == bufsize);
  302. }
  303.  
  304. /* Split into pieces that are as large as possible while still not more
  305.    than NCHARS bytes, and are split on line boundaries except
  306.    where lines longer than NCHARS bytes occur. */
  307.  
  308. static void
  309. line_bytes_split (int nchars)
  310. {
  311.   int n_read;
  312.   char *bp;
  313.   int eof = 0;
  314.   int n_buffered = 0;
  315.   char *buf = (char *) xmalloc (nchars);
  316.  
  317.   do
  318.     {
  319.       /* Fill up the full buffer size from the input file.  */
  320.  
  321.       n_read = stdread (buf + n_buffered, nchars - n_buffered);
  322.       if (n_read < 0)
  323.     error (EXIT_FAILURE, errno, "%s", infile);
  324.  
  325.       n_buffered += n_read;
  326.       if (n_buffered != nchars)
  327.     eof = 1;
  328.  
  329.       /* Find where to end this chunk.  */
  330.       bp = buf + n_buffered;
  331.       if (n_buffered == nchars)
  332.     {
  333.       while (bp > buf && bp[-1] != '\n')
  334.         bp--;
  335.     }
  336.  
  337.       /* If chunk has no newlines, use all the chunk.  */
  338.       if (bp == buf)
  339.     bp = buf + n_buffered;
  340.  
  341.       /* Output the chars as one output file.  */
  342.       cwrite (1, buf, bp - buf);
  343.  
  344.       /* Discard the chars we just output; move rest of chunk
  345.      down to be the start of the next chunk.  Source and
  346.      destination probably overlap.  */
  347.       n_buffered -= bp - buf;
  348.       if (n_buffered > 0)
  349.     memmove (buf, bp, n_buffered);
  350.     }
  351.   while (!eof);
  352.   free (buf);
  353. }
  354.  
  355. int
  356. main (int argc, char **argv)
  357. {
  358.   struct stat stat_buf;
  359.   int num;            /* numeric argument from command line */
  360.   enum
  361.     {
  362.       type_undef, type_bytes, type_byteslines, type_lines, type_digits
  363.     } split_type = type_undef;
  364.   int in_blk_size;        /* optimal block size of input file device */
  365.   char *buf;            /* file i/o buffer */
  366.   int accum = 0;
  367.   char *outbase;
  368.   int c;
  369.   int digits_optind = 0;
  370.  
  371.   program_name = argv[0];
  372.   setlocale (LC_ALL, "");
  373.   bindtextdomain (PACKAGE, LOCALEDIR);
  374.   textdomain (PACKAGE);
  375.  
  376.   /* Parse command line options.  */
  377.  
  378.   infile = "-";
  379.   outbase = "x";
  380.  
  381.   while (1)
  382.     {
  383.       /* This is the argv-index of the option we will read next.  */
  384.       int this_optind = optind ? optind : 1;
  385.       long int tmp_long;
  386.  
  387.       c = getopt_long (argc, argv, "0123456789vb:l:C:", longopts, (int *) 0);
  388.       if (c == EOF)
  389.     break;
  390.  
  391.       switch (c)
  392.     {
  393.     case 0:
  394.       break;
  395.  
  396.     case 'b':
  397.       if (split_type != type_undef)
  398.         usage (2, _("cannot split in more than one way"));
  399.       split_type = type_bytes;
  400.       if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK
  401.           || tmp_long < 0 || tmp_long > INT_MAX)
  402.         usage (2, _("invalid number of bytes"));
  403.       accum = (int) tmp_long;
  404.       break;
  405.  
  406.     case 'l':
  407.       if (split_type != type_undef)
  408.         usage (2, _("cannot split in more than one way"));
  409.       split_type = type_lines;
  410.       if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
  411.           || tmp_long < 0 || tmp_long > INT_MAX)
  412.         usage (2, _("invalid number of lines"));
  413.       accum = (int) tmp_long;
  414.       break;
  415.  
  416.     case 'C':
  417.       if (split_type != type_undef)
  418.         usage (2, _("cannot split in more than one way"));
  419.       split_type = type_byteslines;
  420.       if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK
  421.           || tmp_long < 0 ||  tmp_long > INT_MAX)
  422.         usage (2, _("invalid number of bytes"));
  423.       accum = (int) tmp_long;
  424.       break;
  425.  
  426.     case '0':
  427.     case '1':
  428.     case '2':
  429.     case '3':
  430.     case '4':
  431.     case '5':
  432.     case '6':
  433.     case '7':
  434.     case '8':
  435.     case '9':
  436.       if (split_type != type_undef && split_type != type_digits)
  437.         usage (2, _("cannot split in more than one way"));
  438.       if (digits_optind != 0 && digits_optind != this_optind)
  439.         accum = 0;        /* More than one number given; ignore other. */
  440.       digits_optind = this_optind;
  441.       split_type = type_digits;
  442.       accum = accum * 10 + c - '0';
  443.       break;
  444.  
  445.     case 2:
  446.       verbose = 1;
  447.       break;
  448.  
  449.     default:
  450.       usage (2, (char *)0);
  451.     }
  452.     }
  453.  
  454.   if (show_version)
  455.     {
  456.       printf ("split - %s\n", PACKAGE_VERSION);
  457.       exit (EXIT_SUCCESS);
  458.     }
  459.  
  460.   if (show_help)
  461.     usage (0, (char *)0);
  462.  
  463.   /* Handle default case.  */
  464.   if (split_type == type_undef)
  465.     {
  466.       split_type = type_lines;
  467.       accum = 1000;
  468.     }
  469.  
  470.   if (accum < 1)
  471.     usage (2, _("invalid number"));
  472.   num = accum;
  473.  
  474.   /* Get out the filename arguments.  */
  475.  
  476.   if (optind < argc)
  477.     infile = argv[optind++];
  478.  
  479.   if (optind < argc)
  480.     outbase = argv[optind++];
  481.  
  482.   if (optind < argc)
  483.     usage (2, _("too many arguments"));
  484.  
  485.   /* Open the input file.  */
  486.   if (!strcmp (infile, "-"))
  487.     input_desc = 0;
  488.   else
  489.     {
  490.       input_desc = open (infile, O_RDONLY);
  491.       if (input_desc < 0)
  492.     error (EXIT_FAILURE, errno, "%s", infile);
  493.     }
  494.  
  495.   /* No output file is open now.  */
  496.   output_desc = -1;
  497.  
  498.   /* Copy the output file prefix so we can add suffixes to it.
  499.      26**29 is certainly enough output files!  */
  500.  
  501.   outfile = xmalloc (strlen (outbase) + 30);
  502.   strcpy (outfile, outbase);
  503.   outfile_mid = outfile + strlen (outfile);
  504.   outfile_end = outfile_mid + 2;
  505.   memset (outfile_mid, 0, 30);
  506.   outfile_mid[0] = 'a';
  507.   outfile_mid[1] = 'a' - 1;  /* first call to next_file_name makes it an 'a' */
  508.  
  509.   /* Get the optimal block size of input device and make a buffer.  */
  510.  
  511.   if (fstat (input_desc, &stat_buf) < 0)
  512.     error (EXIT_FAILURE, errno, "%s", infile);
  513.   in_blk_size = ST_BLKSIZE (stat_buf);
  514.  
  515.   buf = xmalloc (in_blk_size + 1);
  516.  
  517.   switch (split_type)
  518.     {
  519.     case type_digits:
  520.     case type_lines:
  521.       lines_split (num, buf, in_blk_size);
  522.       break;
  523.  
  524.     case type_bytes:
  525.       bytes_split (num, buf, in_blk_size);
  526.       break;
  527.  
  528.     case type_byteslines:
  529.       line_bytes_split (num);
  530.       break;
  531.  
  532.     default:
  533.       abort ();
  534.     }
  535.  
  536.   if (close (input_desc) < 0)
  537.     error (EXIT_FAILURE, errno, "%s", infile);
  538.   if (output_desc >= 0 && close (output_desc) < 0)
  539.     error (EXIT_FAILURE, errno, "%s", outfile);
  540.  
  541.   exit (EXIT_SUCCESS);
  542. }
  543.